/* * $Id: PDFGraphics.java,v 1.6 2007/09/22 12:58:40 gil1 Exp $ $Date: 2007/09/22 * 12:58:40 $ This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of the License, * or (at your option) any later version. This library is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the GNU Lesser General Public License for more details. You should have * received a copy of the GNU Lesser General Public License along with this * library; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */package gnu.jpdf;import java.awt.BasicStroke;import java.awt.Color;import java.awt.Composite;import java.awt.Font;import java.awt.FontMetrics;import java.awt.Frame;import java.awt.Graphics;import java.awt.Graphics2D;import java.awt.GraphicsConfiguration;import java.awt.Image;import java.awt.Paint;import java.awt.Polygon;import java.awt.Rectangle;import java.awt.RenderingHints;import java.awt.RenderingHints.Key;import java.awt.Shape;import java.awt.Stroke;import java.awt.font.FontRenderContext;import java.awt.font.GlyphVector;import java.awt.geom.AffineTransform;import java.awt.geom.Area;import java.awt.geom.PathIterator;import java.awt.geom.Point2D;import java.awt.image.BufferedImage;import java.awt.image.BufferedImageOp;import java.awt.image.ColorModel;import java.awt.image.ImageObserver;import java.awt.image.RenderedImage;import java.awt.image.WritableRaster;import java.awt.image.renderable.RenderableImage;import java.awt.print.PageFormat;import java.io.PrintWriter;import java.io.Serializable;import java.text.DecimalFormat;import java.util.Hashtable;import java.util.Map;/** * This class is our implementation of AWT's Graphics class. It provides a Java * standard way of rendering into a PDF Document's Page. *  * @author Peter T Mount, http://www.retep.org.uk/pdf/ * @author Eric Z. Beard, ericzbeard@hotmail.com * @author Gilbert DeLeeuw, gil1@users.sourceforge.net * @version $Revision: 1.6 $, $Date: 2007/09/22 12:58:40 $ * @see gnu.jpdf.PDFGraphics */public class PDFGraphics extends Graphics2D implements Serializable{        /**     * One degree in radians     */    private static final double degrees_to_radians = Math.PI / 180.0;        private static final int FILL = 1;    private static final int STROKE = 2;    private static final int CLIP = 3;        private static final AffineTransform IDENTITY = new AffineTransform();        private static final Stroke DEF_STROKE = new BasicStroke();        /*     * NOTE: The original class is the work of Peter T. Mount, who released it     * in the uk.org.retep.pdf package. It was modified by Eric Z. Beard as     * follows: The package name was changed to gnu.pdf. The formatting was     * changed a little bit. This used to subclass an abstract class in a     * different package with the same name (confusing). Now it's one concrete     * class. drawImage() was implemented It is still licensed under the LGPL.     */    // Implementation notes:    //    // Pages 333-335 of the PDF Reference Manual    //    // Unless absolutely required, use the moveto, lineto and rectangle    // operators to perform those actions.    // They contain some extra optimizations    // which will reduce the output size by up to half in some cases.    //    // About fill operators: For correct operation, any fill operation should    // start with closeBlock(), which will ensure any previous path is    // completed,    // otherwise you may find the fill will include previous items        private static final DecimalFormat df = new DecimalFormat("#.###");        private Color background;        /**     * This is true for any Graphics instance that didn't create the stream.     *      * @see #create     */    private boolean child;        private Area clip;        /**     * This holds the current clipRectangle     */    protected Rectangle clipRectangle;        private Composite composite;        private Graphics2D dg2 = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB)            .createGraphics();        /**     * This is the current font (in Java format)     */    private Font font;        /**     * Part of the optimizer: When true, we are drawing a path.     */    private boolean inStroke;        /**     * Part of the optimizer: When true, we are within a Text Block.     */    private boolean inText; // true if within a Text Block - see newTextBlock()        /**     * The stroke line cap code;     */    private int lineCap = 0;        /**     * The stroke line join code     */    private int lineJoin = 0;        /**     * The stroke line width     */    private float lineWidth = 1.0f;        /**     * Part of the optimizer: The last known moveto/lineto x coordinate     *      * @see #moveto     * @see #lineto     */    private float lx; // last known moveto/lineto coordinates        /**     * Part of the optimizer: The last known moveto/lineto y coordinate     *      * @see #moveto     * @see #lineto     */    private float ly; // last known moveto/lineto coordinates        private float miterLimit = 10.0f;        /**     * Part of the optimizer: When true, the font has changed.     */    private boolean newFont; // true if the font changes - see newTextBlock()        private Stroke originalStroke;        // Original transform    private AffineTransform oTransform;        /**     * This is a reference to the PDFPage we are rendering to.     */    private PDFPage page;        /**     * This is the current pen/fill color     */    private Paint paint;        /**     * This is the current font (in PDF format)     */    private PDFFont pdffont;        /**     * Part of the optimizer: This is written to the stream when the newPath()     * is called. np then clears this value.     */    private String pre_np;        // PDF space transform    private AffineTransform pTransform;        /**     * This is the PrintWriter used to write PDF drawing commands to the Stream     */    private PrintWriter pw;    /**     * RenderingHints     */    private RenderingHints rhints = new RenderingHints(null);        private Stroke stroke;        // Start of Graphics2D properties        // Java space transform    private AffineTransform transform;    /**     * This is used to translate coordinates     */    protected float trax;        /**     * This is used to translate coordinates     */    protected float tray;        /**     * Part of the optimizer: The last x coordinate when rendering text     */    private float tx; // the last coordinate for text rendering        /**     * Part of the optimizer: The last y coordinate when rendering text     */    private float ty; // the last coordinate for text rendering        /**     * @see Graphics2D#addRenderingHints(Map)     */    public void addRenderingHints(Map<?, ?> hints)    {        rhints.putAll(hints);    }        /**     * This produces an arc by breaking it down into one or more Bezier curves.     * It is used internally to implement the drawArc and fillArc methods.     *      * @param axc     *            X coordinate of arc centre     * @param ayc     *            Y coordinate of arc centre     * @param width     *            of bounding rectangle     * @param height     *            of bounding rectangle     * @param ang1     *            Start angle     * @param ang2     *            End angle     * @param clockwise     *            true to draw clockwise, false anti-clockwise     */    public void arc(double axc, double ayc,            double width, double height,            double ang1, double ang2,            boolean clockwise)    {                double adiff;        double x0, y0;        double x3r, y3r;        boolean first = true;                // may not need this        // if( ar < 0 ) {        // ang1 += fixed_180;        // ang2 += fixed_180;        // ar = - ar;        // }                double ang1r = (ang1 % 360.0) * degrees_to_radians;                double sin0 = Math.sin(ang1r);        double cos0 = Math.cos(ang1r);                x0 = axc + width * cos0;        y0 = ayc + height * sin0;                // NB: !clockwise here as Java Space is inverted to User Space        if (!clockwise)        {            // Quadrant reduction            while (ang1 < ang2)                ang2 -= 360.0;            while ((adiff = ang2 - ang1) < -90.0)            {                double w = sin0;                sin0 = -cos0;                cos0 = w;                x3r = axc + width * cos0;                y3r = ayc + height * sin0;                arc_add(first,                        width, height,                        x0, y0,                        x3r, y3r,                        (x0 + width * cos0),                        (y0 + height * sin0));                                x0 = x3r;                y0 = y3r;                ang1 -= 90.0;                first = false;            }        }        else        {            // Quadrant reduction            while (ang2 < ang1)                ang2 += 360.0;            while ((adiff = ang2 - ang1) > 90.0)            {                double w = cos0;                cos0 = -sin0;                sin0 = w;                x3r = axc + width * cos0;                y3r = ayc + height * sin0;                arc_add(first,                        width, height,                        x0, y0,                        x3r, y3r,                        (x0 + width * cos0),                        (y0 + height * sin0));                                x0 = x3r;                y0 = y3r;                ang1 += 90.0;                first = false;            }        }                // Compute the intersection of the tangents.        // We know that -fixed_90 <= adiff <= fixed_90.        double trad = Math.tan(adiff * (degrees_to_radians / 2));        double ang2r = ang2 * degrees_to_radians;        double xt = x0 - trad * width * sin0;        double yt = y0 + trad * height * cos0;        arc_add(first, width, height, x0, y0,                (axc + width * Math.cos(ang2r)),                (ayc + height * Math.sin(ang2r)),                xt, yt);    }        /**     * Used by the arc method to actually add an arc to the path Important: We     * write directly to the stream here, because this method operates in User     * space, rather than Java space.     *      * @param first     *            true if the first arc     * @param w     *            width     * @param h     *            height     * @param x0     *            coordinate     * @param y0     *            coordinate     * @param x3     *            coordinate     * @param y3     *            coordinate     * @param xt     *            coordinate     * @param yt     *            coordinate     */    private void arc_add(boolean first,               double w, double h,               double x0, double y0,               double x3, double y3,               double xt, double yt)    {        double dx = xt - x0, dy = yt - y0;        double dist = dx * dx + dy * dy;        double w2 = w * w, h2 = h * h;        double r2 = w2 + h2;                double fw = 0.0, fh = 0.0;        if (dist < (r2 * 1.0e8))        {            // JM            fw = (w2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / w2)))                    : 0.0;            fh = (h2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / h2)))                    : 0.0;        }                // The path must have a starting point        if (first)            moveto(x0, y0);                double x = x0 + ((xt - x0) * fw);        double y = y0 + ((yt - y0) * fh);        x0 = x3 + ((xt - x3) * fw);        y0 = y3 + ((yt - y3) * fh);                // Finally the actual curve.        curveto(x, y, x0, y0, x3, y3);    }        /**     * This simply draws a White Rectangle to clear the area     *      * @param x     *            coordinate     * @param y     *            coordinate     * @param w     *            width     * @param h     *            height     */    public void clearRect(int x, int y, int w, int h)    {        closeBlock();        pw.print("q 1 1 1 RG ");// save state, set colour to White        drawRect(x, y, w, h);        closeBlock("B Q"); // close fill & stroke, then restore state    }        /**     * @see Graphics2D#clip(Shape)     */    public void clip(Shape s)    {        if (s == null)        {            setClip(null);            return;        }        s = transform.createTransformedShape(s);        if (clip == null)            clip = new Area(s);        else            clip.intersect(new Area(s));        // followPath(s, CLIP);    }        /**     * This extra method allows PDF users to clip to a Polygon.     * <p>     * In theory you could use setClip(), except that java.awt.Graphics only     * supports Rectangle with that method, so we will have an extra method.     *      * @param p     *            Polygon to clip to     */    public void clipPolygon(Polygon p)    {        closeBlock(); // finish off any existing path        polygon(p.xpoints, p.ypoints, p.npoints);        closeBlock("W"); // clip to current path        clipRectangle = p.getBounds();    }        /**     * Clips to a set of coordinates     *      * @param x     *            coordinate     * @param y     *            coordinate     * @param w     *            width     * @param h     *            height     */    public void clipRect(int x, int y, int w, int h)    {        setClip(x, y, w, h);    }        /**     * All functions should call this to close any existing optimized blocks.     */    void closeBlock()    {        closeBlock("S");    }        /**     * <p>     * This is used by code that use the path in any way other than Stroke (like     * Fill, close path & Stroke etc). Usually this is used internally.     * </p>     *      * @param code     *            PDF operators that will close the path     */    void closeBlock(String code)    {        if (inText)        {            pw.println("ET Q");            // setOrientation(); // fixes Orientation matrix        }                if (inStroke)        {            pw.println(code);        }                inStroke = inText = false;    }        /**     * This is unsupported - how do you do this with Vector graphics?     *      * @param x     *            coordinate     * @param y     *            coordinate     * @param w     *            width     * @param h     *            height     * @param dx     *            coordinate     * @param dy     *            coordinate     */    public void copyArea(int x, int y, int w, int h, int dx, int dy)    {        // Hmm... Probably need to keep track of everything        // that has been drawn so far to get the contents of an area    }        // ============ Line operations =======================        /**     * <p>     * This returns a child instance of this Graphics object. As with AWT, the     * affects of using the parent instance while the child exists, is not     * determined.     * </p>     * <p>     * Once complete, the child should be released with it's dispose() method     * which will restore the graphics state to it's parent.     * </p>     *      * @return Graphics object to render onto the page     */    public Graphics create()    {        closeBlock();                PDFGraphics g = createGraphic(page, pw);                // The new instance inherits a few items        // g.media = new Rectangle(media);        g.trax = trax;        g.tray = tray;        g.clipRectangle = new Rectangle(clipRectangle);                return (Graphics) g;    } // end create()        /**     * This method creates a new instance of the class based on the page and a     * print writer.     *      * @param page     *            the page to attach to     * @param pw     *            the <code>PrintWriter</code> to attach to.     */    protected PDFGraphics createGraphic(PDFPage page,                      PrintWriter pw)    {        PDFGraphics g = new PDFGraphics();        g.init(page, pw);        return g;    }        /**     * This extension appends a Bezier curve to the path. The curve extends from     * the current point to (x2,y2) using the current point and (x1,y1) as the     * Bezier control points.     * <p>     * The new current point is (x2,y2)     *      * @param x1     *            Second control point     * @param y1     *            Second control point     * @param x2     *            Destination point     * @param y2     *            Destination point     */    public void curveto(double x1, double y1, double x2, double y2)    {        newPath();        pw.println(cxy(x1, y1) + cxy(x2, y2) + "v");        lx = (float) x2;        ly = (float) y2;    }        /**     * This extension appends a Bezier curve to the path. The curve extends from     * the current point to (x3,y3) using (x1,y1) and (x2,y2) as the Bezier     * control points.     * <p>     * The new current point is (x3,y3)     *      * @param x1     *            First control point     * @param y1     *            First control point     * @param x2     *            Second control point     * @param y2     *            Second control point     * @param x3     *            Destination point     * @param y3     *            Destination point     */    public void curveto(double x1, double y1, double x2, double y2, double x3,            double y3)    {        newPath();        pw.println(cxy(x1, y1) + cxy(x2, y2) + cxy(x3, y3) + "c");        lx = (float) x3;        ly = (float) y3;    }        /**     * This extension appends a Bezier curve to the path. The curve extends from     * the current point to (x2,y2) using the current point and (x1,y1) as the     * Bezier control points.     * <p>     * The new current point is (x2,y2)     *      * @param x1     *            Second control point     * @param y1     *            Second control point     * @param x2     *            Destination point     * @param y2     *            Destination point     */    public void curveto(int x1, int y1, int x2, int y2)    {        newPath();        pw.println(cxy(x1, y1) + cxy(x2, y2) + "v");        lx = x2;        ly = y2;    }        /**     * This extension appends a Bezier curve to the path. The curve extends from     * the current point to (x3,y3) using (x1,y1) and (x2,y2) as the Bezier     * control points.     * <p>     * The new current point is (x3,y3)     *      * @param x1     *            First control point     * @param y1     *            First control point     * @param x2     *            Second control point     * @param y2     *            Second control point     * @param x3     *            Destination point     * @param y3     *            Destination point     */    public void curveto(int x1, int y1, int x2, int y2, int x3, int y3)    {        newPath();        pw.println(cxy(x1, y1) + cxy(x2, y2) + cxy(x3, y3) + "c");        lx = x3;        ly = y3;    }        /**     * This extension appends a Bezier curve to the path. The curve extends from     * the current point to (x2,y2) using (x1,y1) and the end point as the     * Bezier control points.     * <p>     * The new current point is (x2,y2)     *      * @param x1     *            Second control point     * @param y1     *            Second control point     * @param x2     *            Destination point     * @param y2     *            Destination point     */    public void curveto2(double x1, double y1, double x2, double y2)    {        newPath();        pw.println(cxy(x1, y1) + cxy(x2, y2) + "y");        lx = (float) x2;        ly = (float) y2;    }        // Arcs are horrible and complex. They are at the end of the    // file, because they are the largest. This is because, unlike    // Postscript, PDF doesn't have any arc operators, so we must    // implement them by converting into one or more Bezier curves    // (which is how Postscript does them internally).        /**     * This extension appends a Bezier curve to the path. The curve extends from     * the current point to (x2,y2) using (x1,y1) and the end point as the     * Bezier control points.     * <p>     * The new current point is (x2,y2)     *      * @param x1     *            Second control point     * @param y1     *            Second control point     * @param x2     *            Destination point     * @param y2     *            Destination point     */    public void curveto2(int x1, int y1, int x2, int y2)    {        newPath();        pw.println(cxy(x1, y1) + cxy(x2, y2) + "y");        lx = x2;        ly = y2;    }        /**     * Converts the Java space dimension into pdf.     *      * @param w     *            width     * @param h     *            height     * @return String containing the coordinates in PDF space     */    private String cwh(double w, double h)    {        double nw = w, nh = h; // scratch                // switch(mediaRot) {        // case PageFormat.PORTRAIT:        // Portrait        // nw = w;        nh = -h;        // break;        //        // case PageFormat.LANDSCAPE:        // // Landscape        // nw = h;        // nh = w;        // break;        //        // // case 180:        // // // Inverse Portrait        // // nw = -w;        // // //nh = h;        // // break;        //        // case PageFormat.REVERSE_LANDSCAPE:        // // Seascape        // nw = -h;        // nh = -w;        // break;        // }                return "" + df.format(nw) + " " + df.format(nh) + " ";    }        /**     * Converts the Java space dimension into pdf.     *      * @param w     *            width     * @param h     *            height     * @return String containing the coordinates in PDF space     */    private String cwh(int w, int h)    {        return cwh((double) w, (double) h);    }        /**     * Converts the Java space coordinates into pdf.     *      * @param x     *            coordinate     * @param y     *            coordinate     * @return String containing the coordinates in PDF space     */    private String cxy(double x, double y)    {        // double nx = x, ny = y; // scratch        // double mh = page.getPageFormat().getHeight();                Point2D ptSrc = new Point2D.Double(x, y);        Point2D ptDst = new Point2D.Double();        transform.transform(ptSrc, ptDst);                // x += trax;        // y += tray;        //        // nx = x;        // ny = mh - y;        //        // System.out.println("\ncxy(" + ptSrc.getX() + ", " + ptSrc.getY() +        // ")");        // System.out.println("Old [" + nx + "," + ny + "]");        // System.out.println("Trn [" + ptDst.getX() + ", " + ptDst.getY() +        // "]");        //        // return "" + df.format(nx) + " " + df.format(ny) + " ";        return "" + df.format(ptDst.getX()) + " " + df.format(ptDst.getY())                + " ";            }        /**     * Converts the Java space coordinates into pdf.     *      * @param x     *            coordinate     * @param y     *            coordinate     * @return String containing the coordinates in PDF space     */    private String cxy(int x, int y)    {        return cxy((double) x, (double) y);    }        /**     * <p>     * This releases any resources used by this Graphics object. You must use     * this method once finished with it. Leaving it open will leave the PDF     * stream in an inconsistent state, and will produce errors.     * </p>     * <p>     * If this was created with Graphics.create() then the parent instance can     * be used again. If not, then this closes the graphics operations for this     * page when used with PDFJob.     * </p>     * <p>     * When using PDFPage, you can create another fresh Graphics instance, which     * will draw over this one.     * </p>     */    public void dispose()    {        closeBlock();        if (child)        {            pw.println("Q"); // restore graphics context        }        else        {            pw.close(); // close the stream if were the parent        }    }        // *********************************************    // **** Implementation of java.awt.Graphics ****    // *********************************************        // ============ Rectangle operations =======================        /**     * @see Graphics2D#draw(Shape)     */    public void draw(Shape s)    {        followPath(s, STROKE);    }        /**     * <p>     * Not implemented     * </p>     * <p>     * Draws a 3-D highlighted outline of the specified rectangle. The edges of     * the rectangle are highlighted so that they appear to be beveled and lit     * from the upper left corner. The colors used for the highlighting effect     * are determined based on the current color. The resulting rectangle covers     * an area that is width + 1 pixels wide by height + 1 pixels tall.     * </p>     *      * @param x     *            an <code>int</code> value     * @param y     *            an <code>int</code> value     * @param width     *            an <code>int</code> value     * @param height     *            an <code>int</code> value     * @param raised     *            a <code>boolean</code> value     */    public void draw3DRect(int x, int y,             int width, int height, boolean raised)    {        // Not implemented    }        /**     * Draws an arc     *      * @param x     *            coordinate     * @param y     *            coordinate     * @param w     *            width     * @param h     *            height     * @param sa     *            Start angle     * @param aa     *            End angle     */    public void drawArc(int x, int y, int w, int h, int sa, int aa)    {        w = w >> 1;        h = h >> 1;        x += w;        y += h;                arc((double) x, (double) y,                (double) w, (double) h,                (double) -sa, (double) (-sa - aa),                false);    }        /**     * <p>     * Not implemented     * </p>     *      * @param data     *            a <code>byte[]</code> value     * @param offset     *            an <code>int</code> value     * @param length     *            an <code>int</code> value     * @param x     *            an <code>int</code> value     * @param y     *            an <code>int</code> value     */    public void drawBytes(byte[] data, int offset, int length, int x, int y)    {            }        // ============ Optimizers =======================        /**     * @see Graphics2D#drawGlyphVector(GlyphVector, float, float)     */    public void drawGlyphVector(GlyphVector g, float x, float y)    {        Shape s = g.getOutline(x, y);        fill(s);    }        /**     * @see Graphics2D#drawImage(BufferedImage, BufferedImageOp, int, int)     */    public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y)    {        BufferedImage result = img;        if (op != null)        {            result = op.createCompatibleDestImage(img, img.getColorModel());            result = op.filter(img, result);        }        drawImage(result, x, y, null);    }        /**     * @see Graphics2D#drawImage(Image, AffineTransform, ImageObserver)     */    public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs)    {        // return drawImage(img, null, xform, null, obs);        return true;    }        /**     * <p>     * Draw's an image onto the page, with a backing colour.     * </p>     *      * @param img     *            The java.awt.Image     * @param x     *            coordinate on page     * @param y     *            coordinate on page     * @param bgcolor     *            Background colour     * @param obs     *            ImageObserver     * @return true if drawn     */    public boolean drawImage(Image img, int x, int y, Color bgcolor,               ImageObserver obs)    {        return drawImage(img, x, y, img.getWidth(obs), img.getHeight(obs),                bgcolor, obs);    }        /**     * Draw's an image onto the page     *      * @param img     *            The java.awt.Image     * @param x     *            coordinate on page     * @param y     *            coordinate on page     * @param obs     *            ImageObserver     * @return true if drawn     */    public boolean drawImage(Image img, int x, int y, ImageObserver obs)    {        return drawImage(img, x, y, img.getWidth(obs), img.getHeight(obs), obs);    }        /**     * <p>     * Draw's an image onto the page, with a backing colour.     * </p>     *      * @param img     *            The java.awt.Image     * @param x     *            coordinate on page     * @param y     *            coordinate on page     * @param w     *            Width on page     * @param h     *            height on page     * @param bgcolor     *            Background colour     * @param obs     *            ImageObserver     * @return true if drawn     */    public boolean drawImage(Image img, int x, int y, int w, int h,               Color bgcolor, ImageObserver obs)    {        closeBlock();        pw.print("q "); // save state        Color c = getColor(); // save current colour        setColor(bgcolor); // change the colour        drawRect(x, y, w, h);        closeBlock("B Q"); // fill stroke, restore state        paint = c; // restore original colour        return drawImage(img, x, y, img.getWidth(obs), img.getHeight(obs), obs);    }        /**     * <p>     * Draws an image onto the page.     * </p>     * <p>     * This method is implemented with ASCIIbase85 encoding and the zip stream     * deflater. It results in a stream that is anywhere from 3 to 10 times as     * big as the image. This obviously needs some improvement, but it works     * well for small images     * </p>     *      * @param img     *            The java.awt.Image     * @param x     *            coordinate on page     * @param y     *            coordinate on page     * @param w     *            Width on page     * @param h     *            height on page     * @param obs     *            ImageObserver     * @return true if drawn     */    public boolean drawImage(Image img, int x, int y, int w, int h,               ImageObserver obs)    {        closeBlock();        PDFImage image = new PDFImage(img, x, y, w, h, obs);        // The image needs to be registered in several places        page.getPDFDocument().setImageName(image);        page.getPDFDocument().add(image);        page.addToProcset("/ImageC");                // JM        /*         * page.addResource("/XObject << " + image.getName() + " " +         * image.getSerialID() + " 0 R >>");         */        page.addImageResource(image.getName() + " " + image.getSerialID() +                " 0 R");                // q w 0 0 h x y cm % the coordinate matrix        pw.print("q " +                image.getWidth() +                " 0 0 " +                image.getHeight() +                " " + x + " " +                ((int) page.getDimension().getHeight() - y - image.getHeight())                +                " cm \n" + image.getName() + " Do\nQ\n");        return false;    }        /**     * Draw's an image onto the page, with scaling     * <p>     * This is not yet supported.     *      * @param img     *            The java.awt.Image     * @param dx1     *            coordinate on page     * @param dy1     *            coordinate on page     * @param dx2     *            coordinate on page     * @param dy2     *            coordinate on page     * @param sx1     *            coordinate on image     * @param sy1     *            coordinate on image     * @param sx2     *            coordinate on image     * @param sy2     *            coordinate on image     * @param bgcolor     *            Background colour     * @param obs     *            ImageObserver     * @return true if drawn     */    public boolean drawImage(Image img, int dx1, int dy1, int dx2,               int dy2, int sx1, int sy1, int sx2, int sy2,               Color bgcolor, ImageObserver obs)    {        return false;    }        // ============ Clipping operations =======================        /**     * Draw's an image onto the page, with scaling     * <p>     * This is not yet supported.     *      * @param img     *            The java.awt.Image     * @param dx1     *            coordinate on page     * @param dy1     *            coordinate on page     * @param dx2     *            coordinate on page     * @param dy2     *            coordinate on page     * @param sx1     *            coordinate on image     * @param sy1     *            coordinate on image     * @param sx2     *            coordinate on image     * @param sy2     *            coordinate on image     * @param obs     *            ImageObserver     * @return true if drawn     */    public boolean drawImage(Image img, int dx1, int dy1, int dx2,               int dy2, int sx1, int sy1, int sx2, int sy2,               ImageObserver obs)    {        // This shouldn't be too bad, just change the coordinate matrix        return false;    }        /**     * Draws a line between two coordinates. If the first coordinate is the same     * as the last one drawn (i.e. a previous drawLine, moveto, etc) it is     * ignored.     *      * @param x1     *            coordinate     * @param y1     *            coordinate     * @param x2     *            coordinate     * @param y2     *            coordinate     */    public void drawLine(int x1, int y1, int x2, int y2)    {        moveto(x1, y1);        lineto(x2, y2);    }        // ============ Arcs operations ==============================    // These are the standard Graphics operators. They use the    // arc extension operators to achieve the affect.        /**     * <p>     * Draws an oval     * </p>     *      * @param x     *            coordinate     * @param y     *            coordinate     * @param w     *            width     * @param h     *            height     */    public void drawOval(int x, int y, int w, int h)    {        drawArc(x, y, w, h, 0, 360);    }        /**     * Draws a polygon, linking the first and last coordinates.     *      * @param xp     *            Array of x coordinates     * @param yp     *            Array of y coordinates     * @param np     *            number of points in polygon     */    public void drawPolygon(int[] xp, int[] yp, int np)    {        polygon(xp, yp, np);        closeBlock("s"); // close path and stroke    }        /**     * Draws a polyline. The first and last coordinates are not linked.     *      * @param xp     *            Array of x coordinates     * @param yp     *            Array of y coordinates     * @param np     *            number of points in polyline     */    public void drawPolyline(int[] xp, int[] yp, int np)    {        polygon(xp, yp, np);        // no stroke, as we keep the optimizer in stroke state    }        /**     * We override Graphics.drawRect as it doesn't join the 4 lines. Also, PDF     * provides us with a Rectangle operator, so we will use that.     *      * @param x     *            coordinate     * @param y     *            coordinate     * @param w     *            width     * @param h     *            height     */    public void drawRect(int x, int y, int w, int h)    {        newPath();        pw.print(cxy(x, y) + cwh(w, h) + "re "); // rectangle        lx = x; // I don't know if this is correct, but lets see if PDF ends        ly = y; // the rectangle at it's start.        // stroke (optimized)    }        /**     * @see Graphics2D#drawRenderableImage(RenderableImage, AffineTransform)     */    public void drawRenderableImage(RenderableImage img, AffineTransform xform)    {        drawRenderedImage(img.createDefaultRendering(), xform);    }        /**     * @see Graphics2D#drawRenderedImage(RenderedImage, AffineTransform)     */    public void drawRenderedImage(RenderedImage img, AffineTransform xform)    {        BufferedImage image = null;        if (img instanceof BufferedImage)        {            image = (BufferedImage) img;        }        else        {            ColorModel cm = img.getColorModel();            int width = img.getWidth();            int height = img.getHeight();            WritableRaster raster = cm.createCompatibleWritableRaster(width,                    height);            boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();            Hashtable<String, Object> properties = new Hashtable<String, Object>();            String[] keys = img.getPropertyNames();            if (keys != null)            {                for (int i = 0; i < keys.length; i++)                {                    properties.put(keys[i], img.getProperty(keys[i]));                }            }            BufferedImage result = new BufferedImage(cm, raster,                    isAlphaPremultiplied, properties);            img.copyData(raster);            image = result;        }        drawImage(image, xform, null);    }        /**     * This is not yet implemented     *      * @param x     *            coordinate     * @param y     *            coordinate     * @param w     *            width     * @param h     *            height     * @param aw     *            a-width     * @param ah     *            a-height     */    public void drawRoundRect(int x, int y, int w, int h, int aw, int ah)    {    }        // ============ Oval operations =======================        /**     * Draws a string using a AttributedCharacterIterator.     * <p>     * This is not supported yet, as I have no idea what an     * AttributedCharacterIterator is.     * <p>     * This method is new to the Java2 API.     */    public void drawString(java.text.AttributedCharacterIterator aci,             float x, float y)    {    }        /**     * Draws a string using a AttributedCharacterIterator.     * <p>     * This is not supported yet, as I have no idea what an     * AttributedCharacterIterator is.     * <p>     * This method is new to the Java2 API.     */    public void drawString(java.text.AttributedCharacterIterator aci,             int x, int y)    {    }        public void drawString(String s, float x, float y)    {        newTextBlock(x, y);        pw.println(PDFStringHelper.makePDFString(s) + " Tj");    }        /**     * This draws a string.     *      * @oaran s String to draw     * @param x     *            coordinate     * @param y     *            coordinate     */    public void drawString(String s, int x, int y)    {        newTextBlock(x, y);        pw.println(PDFStringHelper.makePDFString(s) + " Tj");    }        /**     * @see Graphics2D#fill(Shape)     */    public void fill(Shape s)    {        followPath(s, FILL);    }        /**     * <p>     * Not implemented     * </p>     *      * @param x     *            an <code>int</code> value     * @param y     *            an <code>int</code> value     * @param width     *            an <code>int</code> value     * @param height     *            an <code>int</code> value     * @param raised     *            a <code>boolean</code> value     */    public void fill3DRect(int x, int y,             int width, int height, boolean raised)    {        // Not implemented    }        /**     * Fills an arc, joining the start and end coordinates     *      * @param x     *            coordinate     * @param y     *            coordinate     * @param w     *            width     * @param h     *            height     * @param sa     *            Start angle     * @param aa     *            End angle     */    public void fillArc(int x, int y, int w, int h, int sa, int aa)    {        // here we fool the optimizer. We force any open path to be closed,        // then draw the arc. Finally, as the optimizer hasn't stroke'd the        // path, we close and fill it, and mark the Stroke as closed.        //        // Note: The lineto to the centre of the object is required, because        // the fill only fills the arc. Skipping this includes an extra        // chord, which isn't correct. Peter May 31 2000        closeBlock();        drawArc(x, y, w, h, sa, aa);        lineto(x + (w >> 1), y + (h >> 1));        closeBlock("b"); // closepath and fill    }        // ============ Extension operations ==============================    // These are extensions, and provide access to PDF Specific    // operators.        /**     * <p>     * Draws a filled oval     * </p>     *      * @param x     *            coordinate     * @param y     *            coordinate     * @param w     *            width     * @param h     *            height     */    public void fillOval(int x, int y, int w, int h)    {        fillArc(x, y, w, h, 0, 360);    }        // ============ Polygon operations =======================        /**     * Fills a polygon.     *      * @param xp     *            Array of x coordinates     * @param yp     *            Array of y coordinates     * @param np     *            number of points in polygon     */    public void fillPolygon(int[] xp, int[] yp, int np)    {        closeBlock(); // finish off any previous paths        polygon(xp, yp, np);        closeBlock("b"); // closepath, fill and stroke    }        // ============ Image operations =======================        /**     * Fills a rectangle with the current colour     *      * @param x     *            coordinate     * @param y     *            coordinate     * @param w     *            width     * @param h     *            height     */    public void fillRect(int x, int y, int w, int h)    {        // end any path & stroke. This ensures the fill is on this        // rectangle, and not on any previous graphics        closeBlock();        drawRect(x, y, w, h);        closeBlock("B"); // rectangle, fill stroke    }        // ============ Round Rectangle operations =======================        /**     * This is not yet implemented     *      * @param x     *            coordinate     * @param y     *            coordinate     * @param w     *            width     * @param h     *            height     * @param aw     *            a-width     * @param ah     *            a-height     */    public void fillRoundRect(int x, int y, int w, int h, int aw, int ah)    {    }        // /////////////////////////////////////////////    //    //    // implementation specific methods    //    //        private void followPath(Shape s, int drawType)    {        PathIterator points;                if (s == null)            return;                if (drawType == STROKE)        {            if (!(stroke instanceof BasicStroke))            {                s = stroke.createStrokedShape(s);                followPath(s, FILL);                return;            }        }        // if (drawType==STROKE) {        // setStrokeDiff(stroke, oldStroke);        // oldStroke = stroke;        // setStrokePaint();        // }        // else if (drawType==FILL)        // setFillPaint();        points = s.getPathIterator(IDENTITY);        int segments = 0;        float[] coords = new float[6];        while (!points.isDone())        {            segments++;            int segtype = points.currentSegment(coords);            switch (segtype)            {                case PathIterator.SEG_CLOSE:                    pw.print("h ");                    break;                                case PathIterator.SEG_CUBICTO:                    curveto(coords[0], coords[1], coords[2], coords[3],                            coords[4], coords[5]);                    break;                                case PathIterator.SEG_LINETO:                    lineto(coords[0], coords[1]);                    break;                                case PathIterator.SEG_MOVETO:                    moveto(coords[0], coords[1]);                    break;                                case PathIterator.SEG_QUADTO:                    curveto(coords[0], coords[1], coords[2], coords[3]);                    break;            }            points.next();        }                switch (drawType)        {            case FILL:                if (segments > 0)                {                    if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD)                        closeBlock("B*");                    else                        closeBlock("B");                }                break;            case STROKE:                if (segments > 0)                    closeBlock("S");                break;            case CLIP:            default: // drawType==CLIP                if (segments == 0)                    drawRect(0, 0, 0, 0);                if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD)                    closeBlock("W*");                else                    closeBlock("W");        }    }        /**     * @see Graphics2D#getBackground()     */    public Color getBackground()    {        return background;    }        /**     * Returns the Shape of the clipping region As my JDK docs say, this may     * break with Java 2D.     *      * @return Shape of the clipping region     */    public Shape getClip()    {        return null;    }        /**     * Returns the Rectangle that fits the current clipping region     *      * @return the Rectangle that fits the current clipping region     */    public Rectangle getClipBounds()    {        return clipRectangle;    }        // ============ Color operations =======================        /**     * Returns the current pen Colour     *      * @return the current pen Colour     */    public Color getColor()    {        return (paint instanceof Color) ? (Color) paint : Color.black;    }        /**     * @see Graphics2D#getComposite()     */    public Composite getComposite()    {        return composite;    }        /**     * @see Graphics2D#getDeviceConfiguration()     */    public GraphicsConfiguration getDeviceConfiguration()    {        return dg2.getDeviceConfiguration();    }        /**     * Return's the current font.     *      * @return the current font.     */    public Font getFont()    {        if (font == null)            setFont(new Font("SansSerif", Font.PLAIN, 12));        return font;    }        /**     * Returns the FontMetrics for a font.     * <p>     * This doesn't work correctly. Perhaps having some way of mapping the base     * 14 fonts to our own FontMetrics implementation?     *      * @param font     *            The java.awt.Font to return the metrics for     * @return FontMetrics for a font     */    public FontMetrics getFontMetrics(Font font)    {        Frame dummy = new Frame();        dummy.addNotify();        Image image = dummy.createImage(100, 100);        if (image == null)        {            System.err.println("getFontMetrics: image is null");        }        Graphics graphics = image.getGraphics();        return graphics.getFontMetrics(font);            }        /**     * @see Graphics2D#getFontRenderContext()     */    public FontRenderContext getFontRenderContext()    {        boolean antialias = RenderingHints.VALUE_TEXT_ANTIALIAS_ON                .equals(getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING));        boolean fractions = RenderingHints.VALUE_FRACTIONALMETRICS_ON                .equals(getRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS));        return new FontRenderContext(new AffineTransform(), antialias,                fractions);    }        /**     * Returns the associated PDFPage for this graphic     *      * @return the associated PDFPage for this graphic     */    public PDFPage getPage()    {        return page;    }        /**     * Returns the current pen Colour     *      * @return the current pen Colour     */    public Paint getPaint()    {        return paint;    }        /**     * @param arg0     *            a key     * @return the rendering hint     */    public Object getRenderingHint(Key arg0)    {        return rhints.get(arg0);    }        /**     * @see Graphics2D#getRenderingHints()     */    public RenderingHints getRenderingHints()    {        return rhints;    }        /**     * @see Graphics2D#getStroke()     */    public Stroke getStroke()    {        return originalStroke;    }        /**     * @see Graphics2D#getTransform()     */    public AffineTransform getTransform()    {        return new AffineTransform(oTransform);    }        /**     * Returns the PrintWriter handling the underlying stream     *      * @return the PrintWriter handling the underlying stream     */    public PrintWriter getWriter()    {        return pw;    }        /**     * @see Graphics2D#hit(Rectangle, Shape, boolean)     */    public boolean hit(Rectangle rect, Shape s, boolean onStroke)    {        if (onStroke)        {            s = stroke.createStrokedShape(s);        }        s = transform.createTransformedShape(s);        Area area = new Area(s);        if (clip != null)            area.intersect(clip);        return area.intersects(rect.x, rect.y, rect.width, rect.height);    }        /**     * This initialises the stream by saving the current graphics state, and     * setting up the default line width (for us). It also sets up the instance     * ready for graphic operations and any optimisations.     * <p>     * For child instances, the stream is already open, so this should keep     * things happy.     */    private void init()    {        PageFormat pf = page.getPageFormat();                // save graphics state (restored by dispose)        if (child)        {            pw.print("q ");        }                // now initialise the instance        // setColor(Color.black);        paint = Color.black;        // possible: if parent.color is not black, then force black?        // must check to see what AWT does?                // Original User Space Transform (identity)        oTransform = new AffineTransform();                // Transform from Java Space to PDF Space        pTransform = new AffineTransform();        pTransform.translate(0, pf.getHeight());        pTransform.scale(1d, -1d);                // Combined Transform User->Java->PDF        transform = new AffineTransform(oTransform);        transform.concatenate(pTransform);                // Set the line width        setStroke(DEF_STROKE);    }        /**     * This is called by PDFPage when creating a Graphcis instance.     *      * @param page     *            The PDFPage to draw onto.     */    protected void init(PDFPage page)    {        this.page = page;                // We are the parent instance        child = false;                // Now create a stream to store the graphics in        PDFStream stream = new PDFStream();                // To view detail in uncompressed format comment out the next line        stream.setDeflate(true);                page.getPDFDocument().add(stream);        page.add(stream);        pw = stream.getWriter();                // initially, we are limited to the page size        clipRectangle = page.getImageableArea();                // finally initialise the stream        init();    }        /**     * This method is used internally by create() and by the PDFJob class     *      * @param page     *            PDFPage to draw into     * @param pw     *            PrintWriter to use     */    protected void init(PDFPage page, PrintWriter pw)    {        this.page = page;        this.pw = pw;                // In this case, we didn't create the stream (our parent did)        // so child is true (see dispose)        child = true;                // finally initialise the stream        init();    }        /**     * This adds a line segment to the current path     *      * @param x     *            coordinate     * @param y     *            coordinate     */    public void lineto(double x, double y)    {        newPath();        // no optimisation here as it may introduce errors on decimal        // coordinates.        pw.print(cxy(x, y) + "l ");        lx = (float) x;        ly = (float) y;    }        /**     * This adds a line segment to the current path     *      * @param x     *            coordinate     * @param y     *            coordinate     */    public void lineto(int x, int y)    {        newPath();        if (lx != x && ly != y)            pw.print(cxy(x, y) + "l ");        lx = x;        ly = y;    }        /**     * This moves the current drawing point.     *      * @param x     *            coordinate     * @param y     *            coordinate     */    public void moveto(double x, double y)    {        newPath();        // no optimisation here as it may introduce errors on decimal        // coordinates.        pw.print(cxy(x, y) + "m ");        lx = (float) x;        ly = (float) y;    }        /**     * This moves the current drawing point.     *      * @param x     *            coordinate     * @param y     *            coordinate     */    public void moveto(int x, int y)    {        newPath();        if (lx != x || ly != y)            pw.print(cxy(x, y) + "m ");        lx = x;        ly = y;    }        /**     * Functions that draw lines should start by calling this. It starts a new     * path unless inStroke is set, in that case it uses the existing path     */    void newPath()    {        if (inText)        {            closeBlock();        }        if (!inStroke)        {            if (pre_np != null)            {                pw.print(pre_np); // this is the prefix set by setOrientation()                pre_np = null;            }            pw.print("n ");        }                inText = false;        inStroke = true;                // an unlikely coordinate to fool the moveto() optimizer        lx = ly = -9999;    }        /**     * <p>     * Functions that draw text should start by calling this. It starts a text     * block (accounting for media orientation) unless we are already in a Text     * block.     * </p>     * <p>     * It also handles if the font has been changed since the current text block     * was started, so your function will be current.     * </p>     *      * @param x     *            x coordinate in java space     * @param y     *            y coordinate in java space     */    void newTextBlock(float x, float y)    {        // close the current path if there is one        if (inStroke)        {            closeBlock();        }        // create the text block if one is not current. If we are, the newFont        // condition at the end catches font changes        if (!inText)        {            // This ensures that there is a font available            getFont();                        pw.print("q BT ");            tx = ty = 0;                        // produce the text matrix for the media            // switch(mediaRot) {            // case PageFormat.PORTRAIT: // Portrait            // //pw.println("1 0 0 1 0 0 Tm");            // break;            //            // case PageFormat.LANDSCAPE: // Landscape            // pw.println("0 1 -1 0 0 0 Tm"); // rotate            // break;            //            // case 180: // Inverted Portrait            // pw.println("1 0 0 -1 0 0 Tm");            // break;            //            // case PageFormat.REVERSE_LANDSCAPE: // Seascape            // pw.println("0 -1 1 0 0 0 Tm"); // rotate            // break;            // }                        // move the text cursor by an absolute amount            pw.print(txy(x, y) + "Td ");                    }        else        {            // move the text cursor by a relative amount            // int ox=x-tx, oy=ty-y;            // pw.print(""+ox+" "+oy+" Td ");            // pw.print(cwh(x-tx,y-ty)+"Td ");            pw.print(twh(x, y, tx, ty) + "Td ");        }                // preserve the coordinates for the next time        tx = x;        ty = y;                if (newFont || !inText)            pw.print(pdffont.getName() + " " + font.getSize() + " Tf ");                // later add colour changes here (if required)                inStroke = newFont = false;        inText = true;    }        /**     * This is used to add a polygon to the current path. Used by drawPolygon(),     * drawPolyline() and fillPolygon() etal     *      * @param xp     *            Array of x coordinates     * @param yp     *            Array of y coordinates     * @param np     *            number of points in polygon     * @see #drawPolygon     * @see #drawPolyline     * @see #fillPolygon     */    public void polygon(int[] xp, int[] yp, int np)    {        // newPath() not needed here as moveto does it ;-)        moveto(xp[0], yp[0]);        for (int i = 1; i < np; i++)            lineto(xp[i], yp[i]);    }        /**     * @see Graphics2D#rotate(double)     */    public void rotate(double theta)    {        transform.rotate(theta);    }        /**     * @see Graphics2D#rotate(double, double, double)     */    public void rotate(double theta, double x, double y)    {        transform.rotate(theta, x, y);    }        /**     * @see Graphics2D#scale(double, double)     */    public void scale(double sx, double sy)    {        transform.scale(sx, sy);        this.stroke = transformStroke(originalStroke);    }        /**     * @see Graphics2D#setBackground(Color)     */    public void setBackground(Color color)    {        background = color;    }        /**     * Clips to a set of coordinates     *      * @param x     *            coordinate     * @param y     *            coordinate     * @param w     *            width     * @param h     *            height     */    public void setClip(int x, int y, int w, int h)    {        clipRectangle = new Rectangle(x, y, w, h);        closeBlock(); // finish off any existing paths        drawRect(x, y, w, h);        closeBlock("W n"); // clip to current path    }        /**     * As my JDK docs say, this may break with Java 2D.     * <p>     * Sets the clipping region to that of a Shape.     *      * @param s     *            Shape to clip to.     */    public void setClip(Shape s)    {        Rectangle r = s.getBounds();        setClip(r.x, r.y, r.width, r.height);    }        /**     * Sets the color for drawing     *      * @param c     *            Color to use     */    public void setColor(Color c)    {        setPaint(c);    }        /**     * @see Graphics2D#setComposite(Composite)     */    public void setComposite(Composite comp)    {        this.composite = comp;    }        /**     * This extension sets the line width to the default of 1mm which is what     * Java uses when drawing to a PrintJob.     */    public void setDefaultLineWidth()    {        closeBlock(); // draw any path before we change the line width        pw.println("1 w");    }        /**     * This sets the font.     *      * @param f     *            java.awt.Font to set to.     */    public void setFont(Font f)    {        // optimize: Save some space if the font is already the current one.        if (font != f)        {            font = f;            pdffont = page.getFont("/Type1", f.getName(), f.getStyle());                        // mark the font as changed            newFont = true;        }    }        private void setLineCap(int cap)    {        int lineCap = 0;        switch (cap)        {            case BasicStroke.JOIN_MITER:                lineCap = 0;                break;            case BasicStroke.JOIN_ROUND:                lineCap = 1;                break;            case BasicStroke.JOIN_BEVEL:                lineCap = 2;                break;        }        if (this.lineCap != lineCap)        {            closeBlock(); // draw any path before we change the line width            this.lineCap = lineCap;            pw.println("" + lineCap + " J");        }    }        private void setLineJoin(int join)    {        int lineJoin = 0;        switch (join)        {            case BasicStroke.JOIN_MITER:                lineJoin = 0;                break;            case BasicStroke.JOIN_ROUND:                lineJoin = 1;                break;            case BasicStroke.JOIN_BEVEL:                lineJoin = 2;                break;        }        if (this.lineJoin != lineJoin)        {            closeBlock(); // draw any path before we change the line width            this.lineJoin = lineJoin;            pw.println("" + lineJoin + " j");        }    }        /**     * This extension allows the width of the drawn line to be set     *      * @param width     *            Line width in pdf graphic units (points)     */    public void setLineWidth(float width)    {        if (width != this.lineWidth)        {            closeBlock(); // draw any path before we change the line width            this.lineWidth = width;            pw.println("" + width + " w");        }    }        private void setMiterLimit(float limit)    {        if (limit != this.miterLimit)        {            closeBlock(); // draw any path before we change the line width            this.miterLimit = limit;            pw.println("" + limit + " M");        }    }        /**     * Sets the paint for drawing     *      * @param paint     *            Paint to use     */    public void setPaint(Paint paint)    {        this.paint = paint;                if (paint instanceof Color)        {            Color c = (Color) paint;            double r = ((double) c.getRed()) / 255.0;            double g = ((double) c.getGreen()) / 255.0;            double b = ((double) c.getBlue()) / 255.0;            closeBlock(); // This ensures any paths are drawn in the previous                          // colours            pw.println("" + r + " " + g + " " + b + " rg "                    + r + " " + g + " " + b + " RG");        }    }        /**     * Not implemented, as this is not supported in the PDF specification.     */    public void setPaintMode()    {    }        /**     * Sets a rendering hint     *      * @param arg0     * @param arg1     */    public void setRenderingHint(Key arg0, Object arg1)    {        if (arg1 != null)        {            rhints.put(arg0, arg1);        }        else        {            rhints.remove(arg0);        }    }        // Add Graphics2D methods.        /**     * @see Graphics2D#setRenderingHints(Map)     */    public void setRenderingHints(Map<?, ?> hints)    {        rhints.clear();        rhints.putAll(hints);    }        /**     * @see Graphics2D#setStroke(Stroke)     */    public void setStroke(Stroke s)    {        originalStroke = s;        this.stroke = transformStroke(s);                if (stroke instanceof BasicStroke)        {            BasicStroke bs = (BasicStroke) stroke;            setLineCap(bs.getEndCap());            setLineJoin(bs.getLineJoin());            setLineWidth(bs.getLineWidth());            setMiterLimit(bs.getMiterLimit());            // TODO: Line dash pattern        }    }        /**     * @see Graphics2D#setTransform(AffineTransform)     */    public void setTransform(AffineTransform t)    {        // Save copy of original transform.        oTransform = t;        // Working copy of transform        transform = new AffineTransform(t);        // Concatenate Java Space to PDF Space transform        transform.concatenate(pTransform);        this.stroke = transformStroke(originalStroke);    }        /**     * Not implemented, as this is not supported in the PDF specification.     *      * @param c1     *            Color to xor with     */    public void setXORMode(Color c1)    {    }        // ============ Text operations =======================        /**     * @see Graphics2D#shear(double, double)     */    public void shear(double shx, double shy)    {        transform.shear(shx, shy);    }        /**     * @see Graphics2D#transform(AffineTransform)     */    public void transform(AffineTransform tx)    {        transform.concatenate(tx);        this.stroke = transformStroke(originalStroke);    }        private Stroke transformStroke(Stroke stroke)    {        if (!(stroke instanceof BasicStroke))            return stroke;        BasicStroke st = (BasicStroke) stroke;        float scale = (float) Math.sqrt(Math.abs(transform.getDeterminant()));        float dash[] = st.getDashArray();        if (dash != null)        {            for (int k = 0; k < dash.length; ++k)                dash[k] *= scale;        }        return new BasicStroke(st.getLineWidth() * scale, st.getEndCap(),                st.getLineJoin(), st.getMiterLimit(), dash, st.getDashPhase()                        * scale);    }        /**     * @see Graphics2D#translate(double, double)     */    public void translate(double tx, double ty)    {        transform.translate(tx, ty);        trax = (float) tx;        tray = (float) ty;    }        /**     * @see Graphics#translate(int, int)     */    public void translate(int x, int y)    {        translate((double) x, (double) y);    }        /**     * Converts the Java space coordinates into pdf text space.     *      * @param x     *            coordinate     * @param y     *            coordinate     * @param tx     *            coordinate     * @param ty     *            coordinate     * @return String containing the coordinates in PDF text space     */    private String twh(float x, float y, float tx, float ty)    {        float nx = x, ny = y;        float ntx = tx, nty = ty;        int mh = (int) page.getPageFormat().getHeight();        int sx = 1, sy = 1;                // switch(mediaRot)        // {        // case PageFormat.PORTRAIT:        // Portrait        // nx = x;        ny = mh - y;        nty = mh - ty;        // break;        //        // case PageFormat.LANDSCAPE:        // // Landscape        // //nx = y;        // //ny = x;        // //ntx = ty;        // //nty = tx;        // //sy=-1;        // nx = x;        // ny = -y;        // ntx = tx;        // nty = -ty;        // //sy=-1;        // break;        //        // case 180:        // // Inverse Portrait        // // to be completed        // nx = mw - x;        // //ny = y;        // break;        //        // case PageFormat.REVERSE_LANDSCAPE:        // // Seascape        // // to be completed        // nx = mw - y;        // ny = mh - x;        // break;        // }                nx = sx * (nx - ntx);        ny = sy * (ny - nty);        return "" + df.format(nx) + " " + df.format(ny) + " ";    }        /**     * Converts the Java space coordinates into pdf text space.     *      * @param x     *            coordinate     * @param y     *            coordinate     * @return String containing the coordinates in PDF text space     */    private String txy(float x, float y)    {        // float nx=x, ny=y;        // int mh = (int) page.getPageFormat().getHeight();                Point2D ptSrc = new Point2D.Float(x, y);        Point2D ptDst = new Point2D.Float();        transform.transform(ptSrc, ptDst);                // // handle any translations        // x+=trax;        // y+=tray;        //        // nx = x;        // ny = mh - y;        //        // System.out.println("\ntxy(" + ptSrc.getX() + ", " + ptSrc.getY() +        // ")");        // System.out.println("Old [" + nx + "," + ny + "]");        // System.out.println("Trn [" + ptDst.getX() + ", " + ptDst.getY() +        // "]");        //        // return ""+df.format(nx)+" "+df.format(ny)+" ";                return "" + df.format(ptDst.getX()) + " " + df.format(ptDst.getY())                + " ";    }    } // end class PDFGraphics